/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2020 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */
#ifndef OSGEARTH_MAPBOXGL_IMAGE_LAYER
#define OSGEARTH_MAPBOXGL_IMAGE_LAYER 1

#include <osgEarth/ImageLayer>
#include <osgEarth/FeatureSource>
#include <osgEarth/JsonUtils>

namespace osgEarth
{
    namespace MapBoxGL
    {
        template< class T >
        class Stop
        {
        public:
            Stop(float z, T v) :
                zoom(z),
                value(std::move(v))
            {
            }

            T value;
            float zoom;
        };

        template<typename T>
        T lerp(float t, const T& a, const T& b)
        {
            return (T)(a + (b - a) * t);
        }

        template<>
        GeoPoint lerp(float t, const GeoPoint& a, const GeoPoint& b)
        {
            return a.interpolate(b, t);
        }

        template< class T >
        class PropertyExpression
        {
            using StopType = Stop< T >;

        public:
            PropertyExpression() = default;
            const std::vector< StopType >& stops() const {
                return _stops;
            }

            std::vector< StopType >& stops() {
                return _stops;
            }

            T evalute(float zoom) const
            {
                if (_stops.empty())
                {
                    return T();
                }
                else if (zoom <= _stops[0].zoom)
                {
                    return _stops[0].value;
                }
                else if (zoom >= _stops.back().zoom)
                {
                    return _stops.back().value;
                }
                else
                {
                    for (unsigned int i = 0; i < _stops.size(); ++i)
                    {
                        const StopType& a = _stops[i];
                        const StopType& b = _stops[i + 1];
                        if (a.zoom <= zoom && b.zoom >= zoom)
                        {
                            // Normalize the value between 0 and 1 for interpolation.
                            float t = (zoom - a.zoom) / (b.zoom - a.zoom);
                            auto val = lerp(t, a.value, b.value);
                            return val;
                        }
                    }
                }

                return T();
            }


        private:
            float _base = 1.0f;
            std::vector< StopType > _stops;
        };

        template< class T >
        class PropertyValue
        {
        public:
            PropertyValue() = default;

            PropertyValue(T constant) :
                _constant(std::move(constant)),
                _isConstant(true)
            {
            }

            PropertyValue(PropertyExpression<T> expression) :
                _expression(std::move(expression)),
                _isConstant(false)
            {
            }

            void setConstant(T constant)
            {
                _constant = std::move(constant);
                _isConstant = true;
            }

            void setExpression(PropertyExpression<T> expression)
            {
                _expression = std::move(expression);
                _isConstant = false;
            }

            bool isConstant() const
            {
                return _isConstant;
            }

            bool isExpression() const
            {
                return !_isConstant;
            }

            T evaluate(float zoom) const
            {
                if (_isConstant)
                {
                    return _constant;
                }
                return _expression.evalute(zoom);
            }

        private:
            T _constant;
            PropertyExpression<T> _expression;
            bool _isConstant = true;
        };



        // https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#paint-property
        class Paint
        {
        public:
            Paint();

            const PropertyValue<Color>& backgroundColor() const;
            PropertyValue<Color>& backgroundColor();

            const PropertyValue<float>& backgroundOpacity() const;
            PropertyValue<float>& backgroundOpacity();

            const PropertyValue<Color>& fillColor() const;
            PropertyValue<Color>& fillColor();

            const PropertyValue<Color>& lineColor() const;
            PropertyValue<Color>& lineColor();

            const PropertyValue<float>& lineWidth() const;
            PropertyValue<float>& lineWidth();

            const optional<std::string>& textField() const;
            optional<std::string>& textField();

            const PropertyValue<Color>& textColor() const;
            PropertyValue<Color>& textColor();

            const PropertyValue<Color>& textHaloColor() const;
            PropertyValue<Color>& textHaloColor();

            const PropertyValue<float>& textSize() const;
            PropertyValue<float>& textSize();

            const optional<std::string>& iconImage() const;
            optional<std::string>& iconImage();

            const optional<std::string>& visibility() const;
            optional<std::string>& visibility();


        private:

            //Background
            PropertyValue<Color> _backgroundColor = PropertyValue<Color>(Color::Transparent);
            PropertyValue<float> _backgroundOpacity = PropertyValue<float>(1.0f);            

            // Fill
            optional<bool> _fillAntialias;
            PropertyValue<Color>  _fillColor = PropertyValue<Color>(Color("#000000"));
            PropertyValue<float> _fillOpacity = PropertyValue<float>(1.0f);

            // Line
            PropertyValue<Color> _lineColor = PropertyValue<Color>(Color::Green);

            PropertyValue<float> _lineWidth = PropertyValue<float>(1.0f);

            // Text
            optional<std::string> _textField;
            PropertyValue<Color> _textColor = PropertyValue<Color>(Color::Black);
            PropertyValue<Color> _textHaloColor = PropertyValue<Color>(Color::Transparent);
            PropertyValue<float> _textSize = PropertyValue<float>(16.0f);

            // Icon
            optional<std::string> _iconImage;

            optional<std::string> _visibility;


        };

        class OSGEARTH_EXPORT StyleSheet
        {
        public:

            class Source
            {
            public:
                Source();

                const std::string& attribution() const;
                std::string& attribution();

                const std::string& url() const;
                std::string& url();

                const std::string& type() const;
                std::string& type();

                const std::string& name() const;
                std::string& name();

                const std::vector< std::string >& tiles() const;
                std::vector< std::string >& tiles();

                FeatureSource* featureSource();
                const FeatureSource* featureSource() const;

                void loadFeatureSource(const std::string& styleSheetURI, const osgDB::Options* options);


            private:


                std::string _attribution;
                std::string _url;
                std::string _type;
                std::string _name;

                std::vector< std::string > _tiles;

                osg::ref_ptr< FeatureSource > _featureSource;
            };

            

            class FilterExpression
            {
            public:
                Json::Value _filter;
            };

            class Layer
            {
            public:
                Layer();

                const std::string& id() const;
                std::string& id();

                const std::string& source() const;
                std::string& source();

                const std::string& sourceLayer() const;
                std::string& sourceLayer();

                const std::string& type() const;
                std::string& type();


                const unsigned int& minZoom() const;
                unsigned int& minZoom();

                const unsigned int& maxZoom() const;
                unsigned int& maxZoom();

                Paint& paint();
                const Paint& paint() const;

                FilterExpression& filter();
                const FilterExpression& filter() const;

            private:
                std::string _id;
                std::string _source;
                std::string _sourceLayer;
                std::string _type;
                unsigned int _minZoom = 0;
                unsigned int _maxZoom = 24;
                Paint _paint;
                FilterExpression _filter;
            };

            const std::string& version() const;

            const std::string& name() const;

            const URI& glyphs() const;

            const URI& sprite() const;


            std::vector< Layer >& layers();
            const std::vector< Layer >& layers() const;

            std::vector< Source >& sources();
            const std::vector< Source >& sources() const;

            const ResourceLibrary* spriteLibrary() const;

            static StyleSheet load(const URI& location, const osgDB::Options* options);

            StyleSheet();

        private:

            std::string _version;
            std::string _name;
            URI _sprite;
            URI _glyphs;

            std::vector< Source > _sources;
            std::vector< Layer > _layers;

            osg::ref_ptr< ResourceLibrary > _spriteLibrary;
            static ResourceLibrary* loadSpriteLibrary(const URI& sprite);
        };
    }


    class OSGEARTH_EXPORT MapBoxGLImageLayer : public osgEarth::ImageLayer
    {
    public: // serialization
        class OSGEARTH_EXPORT Options : public ImageLayer::Options {
        public:
            META_LayerOptions(osgEarth, Options, ImageLayer::Options);
            OE_OPTION(URI, url);
            virtual Config getConfig() const;
        private:
            void fromConfig( const Config& conf );
        };

    public:
        META_Layer(osgEarth, MapBoxGLImageLayer, Options, osgEarth::ImageLayer, MapboxGLImage);

        //! URL of the style url
        void setURL(const URI& value);
        const URI& getURL() const;

        // Opens the layer and returns a status
        virtual Status openImplementation();

        virtual GeoImage createImageImplementation(const TileKey& key, ProgressCallback* progress) const;

    protected: // Layer

        // Called by Map when it adds this layer
        virtual void addedToMap(const class Map*);

        // Called by Map when it removes this layer
        virtual void removedFromMap(const class Map*);

        // post-ctor initialization
        virtual void init();

    protected:

        virtual ~MapBoxGLImageLayer() { }

        osg::observer_ptr< const osgEarth::Map > _map;
        MapBoxGL::StyleSheet _styleSheet;
    };
} // namespace osgEarth

OSGEARTH_SPECIALIZE_CONFIG(osgEarth::MapBoxGLImageLayer::Options);

#endif // OSGEARTH_MAPBOXGL_IMAGE_LAYER
